package com.atguigu.cloud.service.impl;

import com.atguigu.cloud.apis.AccountFeignApi;
import com.atguigu.cloud.apis.StorageFeignApi;
import com.atguigu.cloud.entities.Order;
import com.atguigu.cloud.mapper.OrderMapper;
import com.atguigu.cloud.service.OrderService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

/**
 * @author bairimengchang
 * @version 1.0
 * @project cloud2024
 * @description 不关注业务实现，主要是学习seata的分布式事务
 * @date 2024/12/8
 */
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderMapper orderMapper;

    // feign远程调用账户服务
    @Resource
    private AccountFeignApi accountFeignApi;

    // feign远程调用库存服务
    @Resource
    private StorageFeignApi storageFeignApi;

    // 四步：下订单->减库存->扣余额->改（订单）状态
    @Override
    // 此时，订单模块就是TM事务管理器（表示全局事务），也是其中一个RM资源管理器（分支事务，向TC汇报分支事务状态），TC事务协调器（seata server）
    // 这里的name表示这个全局事务的名字，因为实际开发中，有多个全局事务
    // 注意seata的undo_log本身是一个备份表，记录了执行sql前和后的数据，没有发生异常就正常进行流程，
    // 发生异常则尝试回滚（之所以说是尝试，是因为seata回滚会检查回滚数据是否正确，如果不正确，此时需要人工处理回滚）。最终都会删除这条备份
    // Branch Rollbacking: 172.24.48.1:8091:1630899529325105159 1630899529325105162 jdbc:mysql://localhost:3306/seata_account
    @GlobalTransactional(name = "cleftgou_create_order", rollbackFor = Exception.class)
    public void create(Order order) {

        // xid全局事务id的检查，类似于 applicationContext（spring中管理bean的容器） 应用程序上下文
        String xid = RootContext.getXID();
        // 1. 新建订单
        log.info("-------> 开始新建订单, xid的值是:{}", xid);
        // 初始化订单状态为0 (创建中)
        order.setStatus(0);
        int result = orderMapper.insertSelective(order);
        // 订单创建成功，则获取插入的订单实体对象
        if(result > 0) {
            log.info("-------> 新建订单成功, orderNo:{}", order.getId());
            // 用id主键查询也可以
            Order orderFromDB = orderMapper.selectOne(order);
            // 2. 扣减库存
            log.info("-------> 开始扣减库存, xid的值是:{}", xid);
            storageFeignApi.decrease(orderFromDB.getProductId(), orderFromDB.getCount());
            log.info("-------> 扣减库存成功, xid的值是:{}", xid);
            // 3. 扣减余额
            log.info("-------> 开始扣减余额, xid的值是:{}", xid);
            accountFeignApi.decrease(orderFromDB.getUserId(), orderFromDB.getMoney());
            log.info("-------> 扣减余额成功, xid的值是:{}", xid);
            // 4. 修改订单状态为已完成
            log.info("-------> 修改订单状态开始, xid的值是:{}", xid);
            // 这里设置订单状态为1
            orderFromDB.setStatus(1);

            // 一种麻烦的拼接条件的写法（属于tk.mybatis）
            Example example = new Example(Order.class);
            // 相当于组装where条件，and条件必须根据主键更新，根据用户id更新很完蛋，会导致这个用户的所有订单都变成【已创建】
            // 确保只有在 status 为 0 的情况下才进行更新。这类似于乐观锁中通过版本号或时间戳来检查数据是否被其他事务修改（并发环境下）。
            example.createCriteria()
                    .andEqualTo("id", orderFromDB.getId())
                    .andEqualTo("status", 0);
            // 最终生成的sql：update t_order set status = 1 where id = 1 and status = 0
            orderMapper.updateByExample(orderFromDB, example);

            // 如果不考虑乐观锁，可以直接使用如下代码
            orderMapper.updateByPrimaryKeySelective(orderFromDB);
        }

        // 结束订单创建
        log.info("-------> 订单创建结束, xid的值是:{}", xid);
    }
}
